/*
Sound Pool Class

This class provides a convenient way of managing sounds in an environment, both with 1 and 2 dimensions. It uses the functions found in sound_positioning.bgt to do the actual sound adjustments, so modifying these functions will affect the entire system. The sound_pool_item class holds all the information necessary for one single sound in the game world. Note that you should not make instances of the sound_pool_item class directly but always use the methods and properties provided in the sound_pool class.
*/

#include "sound_positioning.bgt"

class sound_pool_item
{
sound@ handle;
string filename;
string packname;
int x;
int y;
bool looping;
float pan_step;
float volume_step;
float behind_pitch_decrease;
float start_pan;
float start_volume;
float start_pitch;
int left_range;
int right_range;
int backward_range;
int forward_range;
bool is_3d;
bool paused;
bool stationary;
double start_offset;
bool persistent;

sound_pool_item()
{
reset();
}

void reset()
{
if(@handle!=null)
{
handle.close();
}
@handle=null;
filename="";
packname="";
x=0;
y=0;
looping=false;
pan_step=0.0;
volume_step=0.0;
behind_pitch_decrease=0.0;
start_pan=0.0;
start_volume=0.0;
start_pitch=100.0;
left_range=0;
right_range=0;
backward_range=0;
forward_range=0;
is_3d=false;
paused=false;
stationary=false;
start_offset=0.0;
persistent=false;
}

/*
This method updates the sound, checking if it should be closed do to out of earshot conditions etc.
*/
void update(int listener_x, int listener_y, int max_distance)
{
if(@handle==null)
{
return;
}
if(max_distance>0 and looping==true)
{
int total_distance=get_total_distance(listener_x, listener_y);
if(total_distance>max_distance and handle.active==true)
{
handle.close();
return;
}
if(total_distance<=max_distance and handle.active==false)
{
string old_storage=get_sound_storage();
set_sound_storage(packname);
handle.stream(filename);
set_sound_storage(old_storage);
if(handle.active==true)
{
if(start_offset>0)
{
handle.seek(start_offset);
}
update_listener_position(listener_x, listener_y);
if(paused==false)
{
handle.play_looped();
}
}
return;
}
}
update_listener_position(listener_x, listener_y);
}

/*
This method recalculates the position parameters including the ranges, if any. It then calls the appropriate sound positioning function on the sound handle.
*/
void update_listener_position(int listener_x, int listener_y)
{
if(handle.active==false)
{
return;
}
if(stationary==true)
{
return;
}
int delta_left=x-left_range;
int delta_right=x+right_range;
int delta_backward=y-backward_range;
int delta_forward=y+forward_range;
int true_x=listener_x;
int true_y=listener_y;
if(is_3d==false)
{
if(listener_x>=delta_left and listener_x<=delta_right)
{
position_sound_custom_1d(handle, listener_x, listener_x, pan_step, volume_step, start_pan, start_volume);
return;
}
if(listener_x<delta_left)
position_sound_custom_1d(handle, listener_x, delta_left, pan_step, volume_step, start_pan, start_volume);
if(listener_x>delta_right)
position_sound_custom_1d(handle, listener_x, delta_right, pan_step, volume_step, start_pan, start_volume);
return;
}
if(listener_x<delta_left)
true_x=delta_left;
else if(listener_x>delta_right)
true_x=delta_right;
if(listener_y<delta_backward)
true_y=delta_backward;
else if(listener_y>delta_forward)
true_y=delta_forward;
position_sound_custom_2d(handle, listener_x, listener_y, true_x, true_y, pan_step, volume_step, behind_pitch_decrease, start_pan, start_volume, start_pitch);
}

/*
This method returns the total distance between the current sound and the listener in space. This is used to calculate in and out of earshot conditions.
*/
int get_total_distance(int listener_x, int listener_y)
{
if(stationary==true)
{
return 0;
}
int delta_left=x-left_range;
int delta_right=x+right_range;
int delta_backward=y-backward_range;
int delta_forward=y+forward_range;
int true_x=listener_x;
int true_y=listener_y;
int distance=0;
if(is_3d==false)
{
if(listener_x>=delta_left and listener_x<=delta_right)
{
return distance;
}
if(listener_x<delta_left)
distance=delta_left-listener_x;
if(listener_x>delta_right)
distance=listener_x-delta_right;
return distance;
}
if(listener_x<delta_left)
true_x=delta_left;
else if(listener_x>delta_right)
true_x=delta_right;
if(listener_y<delta_backward)
true_y=delta_backward;
else if(listener_y>delta_forward)
true_y=delta_forward;
if(listener_x<true_x)
distance=(true_x-listener_x);
if(listener_x>true_x)
distance=(listener_x-true_x);
if(listener_y<true_y)
distance+=(true_y-listener_y);
if(listener_y>true_y)
distance+=(listener_y-true_y);
return distance;
}
}


/*
This is the actual sound_pool class. For more information on how to use the class, please see the BGT documentation.
*/
class sound_pool
{
int max_distance;
float pan_step;
float volume_step;
float behind_pitch_decrease;

// Default constructor, where we give the user 100 sounds.
sound_pool()
{
items.resize(100);
max_distance=0;
pan_step=1.0;
volume_step=1.0;
behind_pitch_decrease=0.25;
last_listener_x=0;
last_listener_y=0;
highest_slot=0;
clean_frequency=3;
}

// In this constructor the user can specify how many sounds they want.
sound_pool(int number_of_items)
{
items.resize(number_of_items);
max_distance=0;
pan_step=1.0;
volume_step=1.0;
behind_pitch_decrease=0.25;
last_listener_x=0;
last_listener_y=0;
highest_slot=0;
clean_frequency=3;
}

int play_stationary(string filename, bool looping, bool persistent=false)
{
return play_stationary_extended(filename, looping, 0, 0, 0, 100, persistent);
}

int play_stationary_extended(string filename, bool looping, double offset, float start_pan, float start_volume, float start_pitch, bool persistent=false)
{
int slot=reserve_slot();
if(slot==-1)
{
return -1;
}
items[slot].filename=filename;
items[slot].packname=get_sound_storage();
items[slot].looping=looping;
items[slot].stationary=true;
items[slot].start_offset=offset;
items[slot].start_pan=start_pan;
items[slot].start_volume=start_volume;
items[slot].start_pitch=start_pitch;
items[slot].persistent=persistent;
items[slot].handle.stream(items[slot].filename);
if(items[slot].handle.active==false)
{
items[slot].reset();
return -1;
}
if(items[slot].start_offset>0)
{
items[slot].handle.seek(items[slot].start_offset);
}
if(start_pan!=0.0)
{
items[slot].handle.pan=start_pan;
}
if(start_volume<0.0)
{
items[slot].handle.volume=start_volume;
}
items[slot].handle.pitch=start_pitch;
if(looping==true)
{
items[slot].handle.play_looped();
}
else
{
items[slot].handle.play();
}
if(slot>highest_slot)
highest_slot=slot;
return slot;
}

int play_1d(string filename, int listener_x, int sound_x, bool looping, bool persistent=false)
{
return play_extended_1d(filename, listener_x, sound_x, 0, 0, looping, 0, 0, 0, 100, persistent);
}

int play_extended_1d(string filename, int listener_x, int sound_x, int left_range, int right_range, bool looping, double offset, float start_pan, float start_volume, float start_pitch, bool persistent=false)
{
int slot=reserve_slot();
if(slot==-1)
{
return -1;
}
items[slot].filename=filename;
items[slot].packname=get_sound_storage();
items[slot].x=sound_x;
items[slot].y=0;
items[slot].looping=looping;
items[slot].pan_step=pan_step;
items[slot].volume_step=volume_step;
items[slot].behind_pitch_decrease=0.0;
items[slot].start_pan=start_pan;
items[slot].start_volume=start_volume;
items[slot].start_pitch=start_pitch;
items[slot].left_range=left_range;
items[slot].right_range=right_range;
items[slot].backward_range=0;
items[slot].forward_range=0;
items[slot].is_3d=false;
items[slot].start_offset=offset;
items[slot].persistent=persistent;
if(max_distance>0 and items[slot].get_total_distance(listener_x, 0)>max_distance)
{
// We are out of earshot, so we cancel.
if(looping==false)
{
items[slot].reset();
return -2;
}
else
{
last_listener_x=listener_x;
items[slot].handle.pitch=start_pitch;
items[slot].update(listener_x, 0, max_distance);
if(slot>highest_slot)
highest_slot=slot;
return slot;
}
}
items[slot].handle.stream(items[slot].filename);
if(items[slot].handle.active==false)
{
items[slot].reset();
return -1;
}
if(items[slot].start_offset>0)
{
items[slot].handle.seek(items[slot].start_offset);
}
items[slot].handle.pitch=start_pitch;
last_listener_x=listener_x;
items[slot].update(listener_x, 0, max_distance);
if(looping==true)
{
items[slot].handle.play_looped();
}
else
{
items[slot].handle.play();
}
if(slot>highest_slot)
highest_slot=slot;
return slot;
}

int play_2d(string filename, int listener_x, int listener_y, int sound_x, int sound_y, bool looping, bool persistent=false)
{
return play_extended_2d(filename, listener_x, listener_y, sound_x, sound_y, 0, 0, 0, 0, looping, 0, 0, 0, 100, persistent);
}

int play_extended_2d(string filename, int listener_x, int listener_y, int sound_x, int sound_y, int left_range, int right_range, int backward_range, int forward_range, bool looping, double offset, float start_pan, float start_volume, float start_pitch, bool persistent=false)
{
int slot=reserve_slot();
if(slot==-1)
{
return -1;
}
items[slot].filename=filename;
items[slot].packname=get_sound_storage();
items[slot].x=sound_x;
items[slot].y=sound_y;
items[slot].looping=looping;
items[slot].pan_step=pan_step;
items[slot].volume_step=volume_step;
items[slot].behind_pitch_decrease=behind_pitch_decrease;
items[slot].start_pan=start_pan;
items[slot].start_volume=start_volume;
items[slot].start_pitch=start_pitch;
items[slot].left_range=left_range;
items[slot].right_range=right_range;
items[slot].backward_range=backward_range;
items[slot].forward_range=forward_range;
items[slot].is_3d=true;
items[slot].start_offset=offset;
items[slot].persistent=persistent;
if(max_distance>0 and items[slot].get_total_distance(listener_x, listener_y)>max_distance)
{
// We are out of earshot, so we cancel.
if(looping==false)
{
items[slot].reset();
return -2;
}
else
{
last_listener_x=listener_x;
last_listener_y=listener_y;
items[slot].update(listener_x, listener_y, max_distance);
if(slot>highest_slot)
highest_slot=slot;
return slot;
}
}
items[slot].handle.stream(items[slot].filename);
if(items[slot].handle.active==false)
{
items[slot].reset();
return -1;
}
if(items[slot].start_offset>0)
{
items[slot].handle.seek(items[slot].start_offset);
}
last_listener_x=listener_x;
last_listener_y=listener_y;
items[slot].update(listener_x, listener_y, max_distance);
if(looping==true)
{
items[slot].handle.play_looped();
}
else
{
items[slot].handle.play();
}
if(slot>highest_slot)
highest_slot=slot;
return slot;
}

bool sound_is_active(int slot)
{
/*
If the looping parameter is set to true and the sound object is inactive, the sound is still considered to be active as this just means that we are currently out of earshot. A non-looping sound that has finished playing is considered to be dead, and will be cleaned up.
*/
if(verify_slot(slot)==false)
{
return false;
}
if(items[slot].looping==false and @items[slot].handle==null)
{
return false;
}
if(items[slot].looping==false and items[slot].handle.playing==false)
{
return false;
}
return true;
}

bool sound_is_playing(int slot)
{
if(sound_is_active(slot)==false)
{
return false;
}
return items[slot].handle.playing;
}

bool pause_sound(int slot)
{
if(sound_is_active(slot)==false)
{
return false;
}
if(items[slot].paused==true)
{
return false;
}
items[slot].paused=true;
if(items[slot].handle.playing==true)
items[slot].handle.pause();
return true;
}

bool resume_sound(int slot)
{
if(verify_slot(slot)==false)
{
return false;
}
if(items[slot].paused==false)
{
return false;
}
items[slot].paused=false;
if(max_distance>0 and items[slot].get_total_distance(last_listener_x, last_listener_y)>max_distance)
{
if(items[slot].handle.active==true)
items[slot].handle.close();
return true;
}
items[slot].update(last_listener_x, last_listener_y, max_distance);
if(items[slot].handle.active==true and items[slot].handle.playing==false)
{
if(items[slot].looping==true)
{
items[slot].handle.play_looped();
}
else
{
items[slot].handle.play();
}
}
return true;
}

void pause_all()
{
int currently_playing=0;
int current_length=items.length();
for(uint i=0;i<current_length;i++)
{
if(sound_is_playing(i))
currently_playing+=1;
pause_sound(i);
}
}

void resume_all()
{
int currently_playing=0;
int current_length=items.length();
for(uint i=0;i<current_length;i++)
{
resume_sound(i);
if(sound_is_playing(i))
currently_playing+=1;
}
}

void destroy_all()
{
for(uint i=0;i<items.length();i++)
{
items[i].reset();
}
highest_slot=0;
}

void update_listener_1d(int listener_x)
{
update_listener_2d(listener_x, 0);
}

void update_listener_2d(int listener_x, int listener_y)
{
if(items.length()==0)
return;
last_listener_x=listener_x;
last_listener_y=listener_y;
for(uint i=0;i<=highest_slot;i++)
{
items[i].update(listener_x, listener_y, max_distance);
}
}

bool update_sound_1d(int slot, int x)
{
return update_sound_2d(slot, x, 0);
}

bool update_sound_2d(int slot, int x, int y)
{
if(verify_slot(slot)==false)
{
return false;
}
items[slot].x=x;
items[slot].y=y;
items[slot].update(last_listener_x, last_listener_y, max_distance);
return true;
}

bool update_sound_start_values(int slot, float start_pan, float start_volume, float start_pitch)
{
if(verify_slot(slot)==false)
{
return false;
}
items[slot].start_pan=start_pan;
items[slot].start_volume=start_volume;
items[slot].start_pitch=start_pitch;
items[slot].update(last_listener_x, last_listener_y, max_distance);
if(items[slot].stationary==true and @items[slot].handle!=null)
{
items[slot].handle.pan=start_pan;
items[slot].handle.volume=start_volume;
items[slot].handle.pitch=start_pitch;
return true;
}
if(items[slot].is_3d==false and items[slot].handle.pitch!=start_pitch)
{
items[slot].handle.pitch=start_pitch;
}
return true;
}

bool update_sound_range_1d(int slot, int left_range, int right_range)
{
return update_sound_range_2d(slot, left_range, right_range, 0, 0);
}

bool update_sound_range_2d(int slot, int left_range, int right_range, int backward_range, int forward_range)
{
if(verify_slot(slot)==false)
{
return false;
}
items[slot].left_range=left_range;
items[slot].right_range=right_range;
items[slot].backward_range=backward_range;
items[slot].forward_range=forward_range;
items[slot].update(last_listener_x, last_listener_y, max_distance);
return true;
}

bool destroy_sound(int slot)
{
if(verify_slot(slot)==true)
{
items[slot].reset();
if(slot==highest_slot)
find_highest_slot(highest_slot);
return true;
}
return false;
}

// Internal properties.

int last_listener_x;
int last_listener_y;
sound_pool_item[] items;
uint highest_slot;
int clean_frequency;

// Internal methods.

void find_highest_slot(uint limit)
{
/*
If the looping parameter is set to true and the sound object is inactive, the sound is still considered to be active as this just means that we are currently out of earshot. A non-looping sound that has finished playing is considered to be dead, and will be cleaned up.
*/
highest_slot=0;
sound_pool_item@ current;
for(uint i=0;i<limit;i++)
{
@current=@items[i];
if(current.looping==false and @current.handle==null)
{
continue;
}
if(current.looping==false and current.handle.playing==false)
{
continue;
}
highest_slot=i;
}
}

void clean_unused()
{
/*
If the looping parameter is set to true and the sound object is inactive, the sound is still considered to be active as this just means that we are currently out of earshot. A non-looping sound that has finished playing is considered to be dead, and will be cleaned up if it is not set to be persistent.
*/
if(items.length()==0)
return;
sound_pool_item@ current;
uint limit=highest_slot;
bool killed_highest_slot=false;
for(uint i=0;i<=limit;i++)
{
@current=@items[i];
if(current.persistent==true)
{
continue;
}
if(current.looping==true)
{
continue;
}
if(@current.handle==null)
{
continue;
}
if(current.handle.active==false)
{
continue;
}
if(current.handle.playing==false and current.paused==false)
{
if(i==highest_slot)
killed_highest_slot=true;
current.reset();
}
}
if(killed_highest_slot==true)
find_highest_slot(highest_slot);
}

bool verify_slot(int slot)
{
// This is a security function to perform basic sanity checks.
if(slot<0)
{
return false;
}
if(slot>=items.length())
{
return false;
}
if(items[slot].persistent==true)
{
return true;
}
if(items[slot].looping==true)
{
return true;
}
if(@items[slot].handle!=null)
{
return true;
}
return false;
}

int reserve_slot()
{
// This finds the first available sound slot and prepares it for use.

clean_frequency-=1;
if(clean_frequency==0)
{
clean_frequency=3;
clean_unused();
}
int slot=-1;
sound_pool_item@ current;
uint current_length=items.length();
for(uint i=0;i<current_length;i++)
{
@current=@items[i];
if(current.persistent==true)
{
continue;
}
if(current.looping==true)
{
continue;
}
if(@current.handle==null)
{
slot=i;
break;
}
if(current.handle.active==false)
{
slot=i;
break;
}
if(current.handle.playing==false)
{
slot=i;
break;
}
}
if(slot==-1)
{
return -1;
}
current.reset();
sound temp;
@current.handle=temp;
return slot;
}

}
